import rbush from 'rbush' // https://www.5axxw.com/wiki/content/7wjc4t

export var CanvasLabel = (L.CanvasLabel = L.Canvas.extend({
  options: {
    defaultLabelStyle: {
      offsetX: 0, // 横坐标偏移(像素)
      offsetY: 0, // 纵坐标偏移(像素)
      scale: 1, // 放大比例
      rotation: 0, // 旋转角度（弧度），可能会导致碰撞检测不准确
      text: null, // 标注文本内容
      minZoom: null, // 最小显示级别
      maxZoom: null, // 最大显示级别
      collisionFlg: true, // 碰撞检测
      collisionFlg2: false,
      center: null, // 标注位置，默认为null,会自动计算几何中心
      zIndex: 0, // 排序
      defaultHeight: 20, // 文本高度,无法自动计算,所以直接传参手动调整

      // 文本样式,具体值请参考[canvas](https://www.runoob.com/tags/ref-canvas.html)
      font: '10px sans-serif',
      fillStyle: 'rgba(0,0,0,1)',
      lineCap: 'round',
      lineDash: [],
      lineDashOffset: 0,
      lineJoin: 'round',
      strokeStyle: 'rgba(0,0,0,1)',
      textAlign: 'center',
      textBaseline: 'middle',
      lineWidth: 1
    }
  },
  getEvents: function() {
    var events = {
      viewreset: this._reset,
      zoom: this._onZoom,
      moveend: this._update,
      zoomend: this._onZoomEnd,
      click: this._executeListeners,
      mousemove: this._executeListeners,
      mousedown: this._executeListeners,
      mouseup: this._executeListeners
    }
    if (this._zoomAnimated) {
      events.zoomanim = this._onAnimZoom
    }
    return events
  },
  initialize: function(options) {
    const that = this
    this._intervalId = setInterval(function() {
      that._update()
      that._draw()
    }, 1000)
    this._onClickListeners = []
    this._onHoverListeners = []
    this._onMouseDownListeners = []
    this._onMouseUpListeners = []

    options.defaultLabelStyle = options.defaultLabelStyle || {}
    options.defaultLabelStyle = L.extend(
      {},
      this.options.defaultLabelStyle,
      options.defaultLabelStyle
    )
    L.Canvas.prototype.initialize.call(this, options)
  },
  onDestroy: function() {
    // 清除定时器
    if (this._intervalId) {
      clearInterval(this._intervalId)
      this._intervalId = null
    }

    // 如果需要，还可以在这里添加其他销毁时的清理代码
  },
  /**
     * 继承L.Canvas方法
     * 在刷新时保存画布当前地理位置
     */
  _update: function() {
    this._latlngBounds = this._map.getBounds().pad(this.options.padding)
    L.Canvas.prototype._update.call(this)
  },

  _draw: function() {
    if (!this._textBounds) {
      this._textBounds = new rbush()
    } else {
      this._textBounds.clear()
    }
    const drawLayers = []

    var layer
    var bounds = this._redrawBounds
    this._ctx.save()
    if (bounds) {
      var size = bounds.getSize()
      this._ctx.beginPath()
      this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y)
      this._ctx.clip()
    }

    this._drawing = true

    for (var order = this._drawFirst; order; order = order.next) {
      layer = order.layer
      if (
        !bounds ||
                (layer._pxBounds && layer._pxBounds.intersects(bounds))
      ) {
        drawLayers.push(layer)
      }
    }
    for (let i = 0; i < drawLayers.length; i++) {
      drawLayers[i]._updatePath()
    }

    // 筛选需要绘制标注的图层
    const labelLayers = drawLayers.filter(function(layer) {
      {
        return layer.options.labelStyle && layer.options.labelStyle.text
      }
    })
    // 筛选不做碰撞检测的标签图层并安装zIndex排序
    const notCollisionLayers = labelLayers.filter((layer) => {
      var collisionFlg =
                layer.options.labelStyle.collisionFlg != undefined
                  ? layer.options.labelStyle.collisionFlg
                  : this.options.defaultLabelStyle.collisionFlg2
      return collisionFlg != true
    })
    // 不需要做碰撞检测的标注升序排序,zIndex值大的后绘制,会覆盖在先绘制的标注上面
    notCollisionLayers
      .sort((layer1, layer2) => {
        const zIndex1 = layer1.options.labelStyle.zIndex
          ? layer1.options.labelStyle.zIndex
          : this.options.defaultLabelStyle.zIndex
        const zIndex2 = layer2.options.labelStyle.zIndex
          ? layer2.options.labelStyle.zIndex
          : this.options.defaultLabelStyle.zIndex
        return zIndex1 - zIndex2
      })
      .forEach((layer) => {
        this._updateText(this._ctx, layer)
      })

    // 筛选需要碰撞检测的标签图层并安装zIndex排序
    const collisionLayers = labelLayers.filter((layer) => {
      var collisionFlg =
                layer.options.labelStyle.collisionFlg != undefined
                  ? layer.options.labelStyle.collisionFlg
                  : this.options.defaultLabelStyle.collisionFlg
      return collisionFlg == true
    })

    // 需要做碰撞检测的标注降序排序,zIndex值大的优先绘制
    collisionLayers
      .sort((layer1, layer2) => {
        const zIndex1 = layer1.options.labelStyle.zIndex
          ? layer1.options.labelStyle.zIndex
          : this.options.defaultLabelStyle.zIndex
        const zIndex2 = layer2.options.labelStyle.zIndex
          ? layer2.options.labelStyle.zIndex
          : this.options.defaultLabelStyle.zIndex
        return -zIndex1 + zIndex2
      })
      .forEach((layer) => {
        this._updateText(this._ctx, layer)
      })

    this._drawing = false

    this._ctx.restore() // Restore state before clipping.
  },

  /**
     * 更新文本标注
     */
  _updateText: function(ctx, layer) {
    const that = this
    // 没有标签样式或没有标签文本的直接退出
    if (!layer.options.labelStyle || !layer.options.labelStyle.text) {
      return
    }
    // 计算图形中心点
    var latlng = L.latLng(layer.options.labelStyle.center)
    if (latlng) {
    } else if (layer.getLatLng) {
      latlng = layer.getLatLng()
    } else {
      // 线，面没有环的直接退出
      if (layer._parts.length == 0 || layer._parts[0].length == 0) {
        return
      }
      latlng = layer.getCenter()
    }

    // 图形中心点没有在可视区域内的直接退出
    if (!this._latlngBounds.contains(latlng)) {
      return
    }

    let layerLabelStyle = layer.options.labelStyle
    const defaultLabelStyle = L.extend({}, this.options.defaultLabelStyle)
    // 图层标注样式是个函数
    if (typeof layerLabelStyle === 'function') {
      layerLabelStyle = layerLabelStyle(layer)
    }

    // 最终标注样式
    const labelStyle = L.extend(defaultLabelStyle, layer.options.labelStyle)

    // 地图缩放级别小于标注最小显示级别直接退出
    if (labelStyle.minZoom) {
      if (this._map.getZoom() < labelStyle.minZoom) {
        return
      }
    }

    // 地图缩放级别大于标注最大显示级别直接退出
    if (labelStyle.maxZoom) {
      if (this._map.getZoom() > labelStyle.maxZoom) {
        return
      }
    }
    // 保持画布原本样式
    ctx.save()

    // 设置画布样式
    ctx.font = labelStyle.font
    ctx.fillStyle = labelStyle.fillStyle
    ctx.lineCap = labelStyle.lineCap
    ctx.lineDash = labelStyle.lineDash
    ctx.lineDashOffset = labelStyle.lineDashOffset
    ctx.lineJoin = labelStyle.lineJoin
    ctx.strokeStyle = labelStyle.strokeStyle
    ctx.textAlign = labelStyle.textAlign
    ctx.textBaseline = labelStyle.textBaseline
    ctx.lineWidth = labelStyle.lineWidth

    // 标注偏移
    var offsetX = labelStyle.offsetX
    var offsetY = labelStyle.offsetY
    // 相对于原点的相应像素坐标
    var p = this._map.latLngToLayerPoint(latlng)

    // 计算标注像素坐标
    var x = p.x + offsetX
    var y = p.y + offsetY

    // 设置标注坐标为中心点(这样可以直接进行缩放与旋转而不用考虑其他因素，实现后通过还原画布不会影响其他效果)
    ctx.translate(x, y)

    // 缩放比例不为1
    if (labelStyle.scale != 1) {
      ctx.scale(labelStyle.scale, labelStyle.scale)
    }

    // 旋转角度不为0
    if (labelStyle.rotation != 0) {
      ctx.rotate(labelStyle.rotation)
    }

    // 碰撞检测
    var textWidth = ctx.measureText(labelStyle.text).width * labelStyle.scale
    var textHeight = labelStyle.defaultHeight * labelStyle.scale
    let minX, minY, maxX, maxY

    // https://www.runoob.com/tags/canvas-textalign.html
    if (labelStyle.textAlign == 'center') {
      minX = x - textWidth / 2
      maxX = x + textWidth / 2
    } else if (
      labelStyle.textAlign == 'start' ||
            labelStyle.textAlign == 'left'
    ) {
      minX = x
      maxX = x + textWidth
    } else if (
      labelStyle.textAlign == 'end' ||
            labelStyle.textAlign == 'right'
    ) {
      minX = x - textWidth
      maxX = x
    } else {
      console.error(
        'textAlign的值必须是start，end，left，center，right中的一个！'
      )
    }

    // https://www.runoob.com/tags/canvas-textBaseline.html
    if (labelStyle.textBaseline == 'middle') {
      minY = y - textHeight / 2
      maxY = y + textHeight / 2
    } else if (
      labelStyle.textBaseline == 'top' ||
            labelStyle.textBaseline == 'hanging'
    ) {
      minY = y
      maxY = y + textHeight
    } else if (
      labelStyle.textBaseline == 'bottom' ||
            labelStyle.textBaseline == 'alphabetic'
    ) {
      minY = y - textHeight
      maxY = y
    } else {
      console.error(
        'textBaseline的值必须是middle，top，hanging，bottom，alphabetic中的一个！'
      )
    }

    const textBounds = { minX, minY, maxX, maxY, layer }
    if (
      !(
        this._zoom < 19 &&
                labelStyle.collisionFlg == true &&
                this._textBounds.collides(textBounds)
      )
    ) {
      // 绘制标注

      ctx.rotate(-this._map.getBearing() / 57.3)
      ctx.strokeText(labelStyle.text, 0, 0)
      ctx.fillText(labelStyle.text, 0, 0)
      this._textBounds.insert(textBounds)
    }

    // 还原画布样式
    ctx.restore()
  },
  /**
     * 执行侦听器
     */
  _executeListeners: function(event) {
    if (!this._textBounds) return
    var me = this
    var ret = this.getTextByEvent(event)
    if (ret && ret.length > 0) {
      me._map._container.style.cursor = 'pointer'
      if (event.type === 'click') {
        me._onClickListeners.forEach(function(listener) {
          listener(event, ret)
        })
      }
      if (event.type === 'mousemove') {
        me._onHoverListeners.forEach(function(listener) {
          listener(event, ret)
        })
      }
      if (event.type === 'mousedown') {
        me._onMouseDownListeners.forEach(function(listener) {
          listener(event, ret)
        })
      }

      if (event.type === 'mouseup') {
        me._onMouseUpListeners.forEach(function(listener) {
          listener(event, ret)
        })
      }
    } else {
      me._map._container.style.cursor = ''
    }
  },
  /**
     * 添加click侦听器
     */
  addOnClickListener: function(listener) {
    this._onClickListeners.push(listener)
  },

  /**
     * 添加hover侦听器
     */
  addOnHoverListener: function(listener) {
    this._onHoverListeners.push(listener)
  },

  /**
     * 添加mousedown侦听器
     */
  addOnMouseDownListener: function(listener) {
    this._onMouseDownListeners.push(listener)
  },

  /**
     * 添加mouseup侦听器
     */
  addOnMouseUpListener: function(listener) {
    this._onMouseUpListeners.push(listener)
  },
  getTextByEvent(event) {
    var x = event.layerPoint.x
    var y = event.layerPoint.y

    var ret = this._textBounds.search({
      minX: x,
      minY: y,
      maxX: x,
      maxY: y
    })
    return ret
  }
}))

export var canvasLabel = (L.canvasLabel = function(options) {
  return new L.CanvasLabel(options)
})
